﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace StampController.Core
{
    public static class LinqExtension
    {
        /// <summary>
        ///     Sort
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="sortExpression"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string sortExpression)
            where TEntity : class
        {
            if (string.IsNullOrEmpty(sortExpression))
                return source; // nothing to sort on  

            Type entityType = typeof (TEntity);
            const string ascSortMethodName = "OrderBy";
            const string descSortMethodName = "OrderByDescending";
            string[] sortExpressionParts = sortExpression.Split(' ');
            string sortProperty = sortExpressionParts[0];
            string sortMethod = ascSortMethodName;

            if (sortExpressionParts.Length > 1 && sortExpressionParts[1] == "DESC")
                sortMethod = descSortMethodName;

            PropertyInfo property = entityType.GetProperty(sortProperty);
            ParameterExpression parameter = Expression.Parameter(entityType, "p");
            MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, property);
            LambdaExpression orderByExp = Expression.Lambda(propertyAccess, parameter);

            MethodCallExpression resultExp = Expression.Call(
                typeof (Queryable),
                sortMethod,
                new[] {entityType, property.PropertyType},
                source.Expression,
                Expression.Quote(orderByExp)
                );

            return source.Provider.CreateQuery<TEntity>(resultExp);
        }

        /// <summary>
        ///     Paging
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="source"></param>
        /// <param name="page"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static IQueryable<TSource> PageIQuery<TSource>(this IQueryable<TSource> source, int page, int pageSize)
        {
            return source.Skip((page - 1)*pageSize).Take(pageSize).Cast<TSource>();
        }

        public static List<TSource> Page<TSource>(this List<TSource> source, int page, int pageSize)
        {
            return source.Skip((page - 1)*pageSize).Take(pageSize).ToList();
        }

        // Condition

        public static IQueryable<TSource> Between<TSource, TKey>(
            this IQueryable<TSource> source,
            Expression<Func<TSource, TKey>> keySelector,
            TKey low, TKey high) where TKey : IComparable<TKey>
        {
            Expression key = Expression.Invoke(keySelector,
                keySelector.Parameters);
            Expression lowerBound = Expression.LessThanOrEqual
                (Expression.Constant(low), key);
            Expression upperBound = Expression.LessThanOrEqual
                (key, Expression.Constant(high));
            Expression and = Expression.AndAlso(lowerBound, upperBound);
            Expression<Func<TSource, bool>> lambda =
                Expression.Lambda<Func<TSource, bool>>(and, keySelector.Parameters);
            return source.Where(lambda);
        }

        public static IQueryable<TSource> WhereIf<TSource>(
            this IQueryable<TSource> source, bool condition,
            Expression<Func<TSource, bool>> predicate)
        {
            if (condition)
                return source.Where(predicate);
            return source;
        }

        public static IEnumerable<TSource> WhereIf<TSource>(
            this IEnumerable<TSource> source, bool condition,
            Func<TSource, bool> predicate)
        {
            if (condition)
                return source.Where(predicate);
            return source;
        }
    }
}